﻿//Copyright (c) Microsoft Corporation.  All rights reserved.

using Microsoft.Ted.Wacel.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Microsoft.Ted.Wacel
{
    public class DataProviderChain: IDataProvider
    {
        private bool mStrongConsistency = true;
        private List<DataProviderChainNode> mNodes = new List<DataProviderChainNode>();
        public DataProviderChain(bool strongConsistancy = true, params DataProviderChainNode[] nodes)
        {
            if (nodes == null || nodes.Length == 0)
                throw new ArgumentNullException("Provider Chain is null or empty.");
            for (int i = 0; i < nodes.Length; i++)
            {
                nodes[i].DataProvider.Configuration = nodes[i].DataProviderConfiguration;
                mNodes.Add(nodes[i]);
            }
            mStrongConsistency = strongConsistancy;
        }

        public void Put(string key, object value, string partition = null)
        {
            Task[] tasks = new Task[mNodes.Count];
            for (int i = 0; i < mNodes.Count; i++)
                tasks[i] = Task.Factory.StartNew((index) => mNodes[(int)index].DataProvider.Put(key, value, partition), i);
            try
            {
                Task.WaitAll(tasks);
            }
            catch (AggregateException exp)
            {
                if (mStrongConsistency || exp.InnerExceptions.Count == mNodes.Count)
                    throw exp.Flatten();
            }
        }

        public void BulkPut(IEnumerable<KeyValuePair<string, object>> values,string partition = null)
        {
            Task[] tasks = new Task[mNodes.Count];
            for (int i = 0; i < mNodes.Count; i++)
                tasks[i] = Task.Factory.StartNew((index) => mNodes[(int)index].DataProvider.BulkPut(values, partition), i);
            try
            {
                Task.WaitAll(tasks);
            }
            catch (AggregateException exp)
            {
                if (mStrongConsistency || exp.InnerExceptions.Count == mNodes.Count)
                    throw exp.Flatten();
            }
        }

        public void Update(string key, object value, string partition = null)
        {
            Task[] tasks = new Task[mNodes.Count];
            for (int i = 0; i < mNodes.Count; i++)
                tasks[i] = Task.Factory.StartNew((index) => mNodes[(int)index].DataProvider.Update(key, value, partition), i);
            try
            {
                Task.WaitAll(tasks);
            }
            catch (AggregateException exp)
            {
                if (mStrongConsistency || exp.InnerExceptions.Count == mNodes.Count)
                    throw exp.Flatten();
            }
        }
        public IEnumerable<KeyValuePair<string,object>> GetAll(string partition = null, Type typeHint = null, Type typeHint2 = null)
        {
            if (mStrongConsistency)
            {
                Exception firstException = null;
                foreach (var node in mNodes)
                {
                    try
                    {
                        return node.DataProvider.GetAll(partition, typeHint);
                    }
                    catch (Exception exp)
                    {
                        if (firstException == null)
                            firstException = exp;
                    }
                }
                if (firstException != null)
                    throw firstException;
                else
                    throw new Exception("Provider chain exhausted.");
            }
            else
            {
                Task[] tasks = new Task[mNodes.Count];
                List<IEnumerable<KeyValuePair<string, object>>> values = new List<IEnumerable<KeyValuePair<string, object>>>();
                for (int i = 0; i < mNodes.Count; i++)
                {
                    values[i] = new List<KeyValuePair<string, object>>();
                    tasks[i] = Task.Factory.StartNew((index) => values.Add(mNodes[(int)index].DataProvider.GetAll(partition, typeHint)), i);
                }
                try
                {
                    Task.WaitAll(tasks);
                }
                catch (AggregateException exp)
                {
                    if (exp.InnerExceptions.Count == mNodes.Count)
                        throw exp.Flatten();
                }
                if (values.Count > 0)
                {
                    if (values[0].Count() > 0 && values[0].First().Value is IVersioned)
                    {
                        return from a in values
                               select (
                                   from b in a
                                   orderby ((IVersioned)b.Value).Version
                                   descending
                                   select b).First();
                    }
                    else
                        return values[0];
                }
                else
                    throw new Exception("Provider chain exhausted.");
            }
        }
        public IEnumerable<KeyValuePair<string, object>> BulkGet(List<string> keys, string partition = null, Type typeHint = null, Type typeHint2 = null)
        {
            if (mStrongConsistency)
            {
                Exception firstException = null;
                foreach (var node in mNodes)
                {
                    try
                    {
                        return node.DataProvider.BulkGet(keys,partition, typeHint);
                    }
                    catch (Exception exp)
                    {
                        if (firstException == null)
                            firstException = exp;
                    }
                }
                if (firstException != null)
                    throw firstException;
                else
                    throw new Exception("Provider chain exhausted.");
            }
            else
            {
                Task[] tasks = new Task[mNodes.Count];
                List<IEnumerable<KeyValuePair<string,object>>> values = new List<IEnumerable<KeyValuePair<string,object>>>();
                for (int i = 0; i < mNodes.Count; i++)
                {
                    values[i] = new List<KeyValuePair<string,object>>();
                    tasks[i] = Task.Factory.StartNew((index) => values.Add(mNodes[(int)index].DataProvider.BulkGet(keys, partition, typeHint)), i);
                }
                try
                {
                    Task.WaitAll(tasks);
                }
                catch (AggregateException exp)
                {
                    if (exp.InnerExceptions.Count == mNodes.Count)
                        throw exp.Flatten();
                }
                if (values.Count > 0)
                {
                    if (values[0].Count()>0 && values[0].First().Value is IVersioned)
                    {
                        return  from a in values  
                                select (
                                    from b in a
                                        orderby ((IVersioned)b.Value).Version 
                                        descending select b).First();
                    }
                    else
                        return values[0];
                }
                else
                    throw new Exception("Provider chain exhausted.");
            }
        }
        public object Get(string key, string partition = null, Type typeHint = null, Type typeHint2 = null)
        {
            if (mStrongConsistency)
            {
                Exception firstException = null;
                foreach (var node in mNodes)
                {
                    try
                    {
                        return node.DataProvider.Get(key,partition, typeHint, typeHint2);
                    }
                    catch (Exception exp)
                    {
                        if (firstException == null)
                            firstException = exp;
                    }
                }
                if (firstException != null)
                    throw firstException;
                else
                    throw new Exception("Provider chain exhausted.");
            }
            else
            {
                Task[] tasks = new Task[mNodes.Count];
                List<object> values = new List<object>();
                for (int i = 0; i < mNodes.Count; i++)
                    tasks[i] = Task.Factory.StartNew((index) => values.Add(mNodes[(int)index].DataProvider.Get(key,partition, typeHint, typeHint2)),i);
                try
                {
                    Task.WaitAll(tasks);
                }
                catch (AggregateException exp)
                {
                    if (exp.InnerExceptions.Count == mNodes.Count)
                        throw exp.Flatten();
                }
                if (values.Count  > 0)
                {
                    if (values[0] is IVersioned)
                        return (from a in values orderby ((IVersioned)a).Version descending select a).First();
                    else
                        return values[0];
                }
                else
                    throw new Exception("Provider chain exhausted.");
            }
        }

        public void Clear()
        {
            Task[] tasks = new Task[mNodes.Count];
            for (int i = 0; i < mNodes.Count; i++ )
                tasks[i] = Task.Factory.StartNew((index) => mNodes[(int)index].DataProvider.Clear(),i);
            try
            {
                Task.WaitAll(tasks);
            }
            catch (AggregateException exp)
            {
                if (mStrongConsistency || exp.InnerExceptions.Count == mNodes.Count)
                    throw exp.Flatten();
            }
        }

        public void Delete(string key, string partition = null)
        {
            Task[] tasks = new Task[mNodes.Count];
            for (int i = 0; i < mNodes.Count; i++)
                tasks[i] = Task.Factory.StartNew((index) => mNodes[(int)index].DataProvider.Delete(key,partition), i);
            try
            {
                Task.WaitAll(tasks);
            }
            catch (AggregateException exp)
            {
                if (mStrongConsistency || exp.InnerExceptions.Count == mNodes.Count)
                    throw exp.Flatten();
            }
        }
        public DataProviderConfiguration Configuration
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
            }
        }
        public IDataProvider Clone(string newName)
        {
            throw new NotSupportedException();
        }
    }
}
